package com.wolfking.mask;

import com.google.common.collect.Maps;
import com.google.gson.*;
import com.google.gson.internal.bind.ObjectTypeAdapter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.text.DateFormat;
import java.util.*;

@Slf4j
@Aspect
@Component
@Order(-100)
public class MaskAnnotationAspect implements InitializingBean {

    private final Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new DateSerializer())
            .setDateFormat(DateFormat.LONG).registerTypeAdapter(Date.class, new DateDeserializer())
            .setDateFormat(DateFormat.LONG).create();

    /**
     * 方法上的处理
     *
     * @param pjp            切面的上下文
     * @param maskAnnotation 注解的实体
     * @return
     * @throws Throwable
     */
    @SuppressWarnings("all")
    @Around("@annotation(maskAnnotation)")
    public Object annotation(ProceedingJoinPoint pjp, MaskAnnotation maskAnnotation) throws Throwable {

        Object obj = pjp.proceed();
        if (obj != null) {
            /* 获取当前切面的method **/
            Method method = MethodSignature.class.cast(pjp.getSignature()).getMethod();
            /* 获取当前切面的Class **/
            Class<?> clazz = pjp.getTarget().getClass();
            String[] strings = maskAnnotation.maskConfig();
            if (strings.length > 0) {
                if (strings.length % 2 == 0) {
                    Map<String, String> config = Maps.newHashMap();
                    for (int i = 0; i < strings.length; i = i + 2) {
                        config.put(strings[i], strings[i + 1]);
                    }
                    String responseJson = obj instanceof String ? (String) obj : gson.toJson(obj);
                    String afterMask = DataMaskUtils.getMaskString(responseJson, config,
                            obj instanceof String ? maskAnnotation.format() : false);
                    if (log.isDebugEnabled()) {
                        log.debug("mask config is {} before mask is {},after mask is {}", config, responseJson,
                                afterMask);
                    }
                    if (obj instanceof String) {
                        return afterMask;
                    } else {
                        if (StringUtils.isNotBlank(afterMask)) {
                            Type genericReturnType = method.getGenericReturnType();
                            try {
                                return gson.fromJson(afterMask, genericReturnType);
                            } catch (Exception e) {
                                log.error("mask error", e);
                            }
                        }
                    }
                } else {
                    log.error("{}#{} config is not available {}", clazz.getName(), method.getName(),
                            Arrays.toString(strings));
                }
            } else {
                log.warn("config is empty class is {}#{}", clazz.getName(), method.getName());
            }
        }
        return obj;
    }

    @Override
    @SuppressWarnings("all")
    public void afterPropertiesSet() throws Exception {
        try {
            Field factories = Gson.class.getDeclaredField("factories");
            factories.setAccessible(true);
            Object o = factories.get(gson);
            Class<?>[] declaredClasses = Collections.class.getDeclaredClasses();
            for (Class c : declaredClasses) {
                if ("java.util.Collections$UnmodifiableList".equals(c.getName())) {
                    Field listField = c.getDeclaredField("list");
                    listField.setAccessible(true);
                    List<TypeAdapterFactory> list = (List<TypeAdapterFactory>) listField.get(o);
                    int i = list.indexOf(ObjectTypeAdapter.getFactory(ToNumberPolicy.DOUBLE));
                    list.set(i, MapTypeAdapter.FACTORY);
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static class DateSerializer implements JsonSerializer<Date> {
        public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
            return new JsonPrimitive(src.getTime());
        }
    }

    private static class DateDeserializer implements JsonDeserializer<Date> {
        public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
                throws JsonParseException {
            return new Date(json.getAsJsonPrimitive().getAsLong());
        }
    }
}
